#ifndef MATRIX_H
#define MATRIX_H

namespace cg
{

	//	matrix is column-major
	class Mat4
	{
	public:
						Mat4( void );
						Mat4( const Vec4& x, const Vec4& y, const Vec4& z, const Vec4& w );

		const Vec4 &	operator[]( int index ) const;
		Vec4 &			operator[]( int index );
		Mat4			operator*( const Mat4 &a ) const;
		Mat4 &			operator*=( const Mat4 &a );
		Vec4			operator*( const Vec4 &v ) const;
		//
		float*			toFloat( void );
		const float*	toFloat( void ) const;

	private:
		Vec4 mat[ 4 ];
	};

	extern const Mat4 MAT4_ZERO;		

	CG_INLINE Mat4::Mat4( void )
	{
		
	}

	CG_INLINE Mat4::Mat4( const Vec4& x, const Vec4& y, const Vec4& z, const Vec4& w )
	{
		this->mat[ 0 ] = x;
		this->mat[ 1 ] = y;
		this->mat[ 2 ] = z;
		this->mat[ 3 ] = w;
	}

	CG_INLINE const Vec4 & Mat4::operator[]( int index ) const
	{
		return mat[ index ];
	}

	CG_INLINE Vec4 & Mat4::operator[]( int index )
	{
		return mat[ index ];
	}

	CG_INLINE Mat4 Mat4::operator*( const Mat4 &right ) const 
	{
		int i, j;
		const float *m1Ptr, *m2Ptr;
		float *dstPtr;
		Mat4 dst;

		m2Ptr = reinterpret_cast<const float *>( this );
		m1Ptr = reinterpret_cast<const float *>( &right );
		dstPtr = reinterpret_cast<float *>( &dst );

		for ( i = 0; i < 4; i++ ) {
			for ( j = 0; j < 4; j++ ) {
				*dstPtr = m1Ptr[ 0 ] * m2Ptr[ 0 * 4 + j ]
						+ m1Ptr[ 1 ] * m2Ptr[ 1 * 4 + j ]
						+ m1Ptr[ 2 ] * m2Ptr[ 2 * 4 + j ]
						+ m1Ptr[ 3 ] * m2Ptr[ 3 * 4 + j ];
				dstPtr++;
			}
			m1Ptr += 4;
		}
		return dst;
	}

	CG_INLINE Mat4 &Mat4::operator*=( const Mat4 &a )
	{
		*this = (*this) * a;
		return *this;
	}

	CG_INLINE Vec4 Mat4::operator*( const Vec4 &v ) const
	{
		const float *mPtr = reinterpret_cast<const float *>( this );		
		Vec4 result;
		float* pRes = result.toFloat();
		for ( int i = 0; i < 4; i++ ) {
			pRes[ i ] = Vec4( mPtr[ i ], mPtr[ 4 + i ], mPtr[ 8 + i ], mPtr[ 12 + i ] ).DotProduct( v );
		}
		return result;
	}

	CG_INLINE float* Mat4::toFloat( void )
	{
		return &mat[ 0 ].x;
	}

	CG_INLINE const float* Mat4::toFloat( void ) const
	{
		return &mat[ 0 ].x;
	}

	CG_INLINE float* toFloat( Mat4& m )
	{
		return &m[ 0 ].x;
	}

	CG_INLINE const float* toFloat( const Mat4& m )
	{
		return &m[ 0 ].x;
	}

	char* toChar( const Mat4& m );

	//
	CG_INLINE void LoadZero( Mat4 &m )
	{
		m = MAT4_ZERO;
	}

	CG_INLINE void LoadIdentity( Mat4 &m )
	{
		m[ 0 ].x = 1.0f;
		m[ 0 ].y = 0.0f;
		m[ 0 ].z = 0.0f;
		m[ 0 ].w = 0.0f;

		m[ 1 ].x = 0.0f;
		m[ 1 ].y = 1.0f;
		m[ 1 ].z = 0.0f;
		m[ 1 ].w = 0.0f;

		m[ 2 ].x = 0.0f;
		m[ 2 ].y = 0.0f;
		m[ 2 ].z = 1.0f;
		m[ 2 ].w = 0.0f;

		m[ 3 ].x = 0.0f;
		m[ 3 ].y = 0.0f;
		m[ 3 ].z = 0.0f;
		m[ 3 ].w = 1.0f;
	}

	// 

	CG_INLINE void ScaleMatrix( Mat4 &m, const Vec3 &scale )
	{
		m[ 0 ].x = scale.x;
		m[ 0 ].y = 0.0f;
		m[ 0 ].z = 0.0f;
		m[ 0 ].w = 0.0f;

		m[ 1 ].x = 0.0f;
		m[ 1 ].y = scale.y;
		m[ 1 ].z = 0.0f;
		m[ 1 ].w = 0.0f;

		m[ 2 ].x = 0.0f;
		m[ 2 ].y = 0.0f;
		m[ 2 ].z = scale.z;
		m[ 2 ].w = 0.0f;

		m[ 3 ].x = 0.0f;
		m[ 3 ].y = 0.0f;
		m[ 3 ].z = 0.0f;
		m[ 3 ].w = 1.0f;		
	}

	CG_INLINE Mat4 ScaleMatrix( const Vec3 &scale )
	{
		Mat4 m;
		ScaleMatrix( m, scale );
		return m;
	}

	CG_INLINE void TranslationMatrix( Mat4 &m, const Vec3 &translate )
	{
		LoadIdentity( m );
		m[ 3 ] += toVec4( translate );		
	}

	CG_INLINE Mat4 TranslationMatrix( const Vec3 &translate )
	{
		Mat4 m;
		TranslationMatrix( m, translate );
		return m;
	}

	void RotationMatrix( Mat4 &m, float angle, const Vec3 &axis );

	CG_INLINE Mat4 RotationMatrix(float angle, const Vec3 &axis )
	{
		Mat4 m;
		RotationMatrix( m, angle, axis );
		return m;
	}
}

#endif